/*
MIT License

Copyright (c) 2021 МГТУ им. Н.Э. Баумана, кафедра ИУ-6, Михаил Фетисов,

https://bmstu.codes/lsx/simodo/stars
*/

#include "simodo/module/HardModuleLoader.h"
#include "simodo/inout/format/fmt.h"

#include <unordered_map>
#include <filesystem>
#include <cassert>

#include <boost/dll/import.hpp>

using namespace simodo;
using namespace simodo::module;

namespace dll = boost::dll;
namespace fs = std::filesystem;

#define CATCH_ERRORS

namespace 
{
    static std::unordered_map<std::string,std::function<ExtModuleFactory_t>> hard_factories;

}

std::shared_ptr<variable::Module_interface> HardModuleLoader::load(const std::string & module_name,
            interpret::Interpret_interface * interpret)
{
    _last_error = "";
    auto it = hard_factories.find(module_name);

    if (it != hard_factories.end()) 
        return std::make_shared<HardModule>(it->second);
    
    std::function<ExtModuleFactory_t> creator = nullptr;

    for(const std::string & dir : _module_places) {
        fs::path path_to_folder { dir };
        fs::path path_to_module { path_to_folder / (module_name + ".simodo-module") };

        if (fs::exists(path_to_module)) {
#ifdef CATCH_ERRORS
            try {
#endif
                creator = loadPluginFactory(path_to_module.string(),"create_simodo_module");
#ifdef CATCH_ERRORS
            }
            catch(const std::runtime_error & e) {
                _last_error = e.what();
                creator = nullptr;
            }
#endif
            if (creator) {
                auto [it,ok] = hard_factories.insert({module_name, creator});
                if (ok)
                    return std::make_shared<HardModule>(creator,interpret);
            }

            /// @note Если модуль (точнее, его фабрика) найден, но не может быть загружен - это ошибка, 
            /// а не повод продолжить поиски.
            break;
        }
    }

    if (_last_error.empty())
        _last_error = inout::fmt("Module '%1' was not found").arg(module_name);

    return std::shared_ptr<variable::Module_interface>();
}

std::function<ExtModuleFactory_t> HardModuleLoader::loadPluginFactory(const std::string & path, 
                                                                      const std::string & alias_name)
{
    return dll::import_alias<ExtModuleFactory_t>(path.c_str(), alias_name);
}
